﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.OleDb;
using System.Windows.Forms;
using System.Collections;
using System.Text.RegularExpressions;

namespace TresselLS
{
    public class TresselLinker
    {
        // LOAD MODULE FORMAT VARIABLES //
        /*#
        Variable Name: headerRecord
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: This is the actual Linking Header Record
        #*/
        private string headerRecord;
        /*#
        Variable Name: textRecords
        Module Where Defined: TresselLinker
        Data Type: ArrayList
        Local or Global: Global
        Purpose: Is an array of textRecord objects
        #*/
        private ArrayList textRecords;
        /*#
        Variable Name: endRecord
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: This is the actual Linking End Record
        #*/
        private string endRecord;


        /*#
        Variable Name: errorFlag
        Module Where Defined: TresselLinker
        Data Type: bool
        Local or Global: Global
        Purpose: Boolean value/flag that indicates whether or not an error has been found in the linker.
        #*/
        private bool errorFlag = false;
        /*#
        Variable Name: fatalErrorFlag
        Module Where Defined: TresselLinker
        Data Type: bool
        Local or Global: Global
        Purpose: Boolean value/flag that indicates whether or not a fatal error has been found in the linker.
        #*/
        private bool fatalErrorFlag = false;
        /*#
        Variable Name: objectSourceArray
        Module Where Defined: TresselLinker
        Data Type: string
        Local or Global: Global
        Purpose: Holds the original object source code in an array.
        #*/
        public string[] objectSourceArray;
        /*#
        Variable Name: linkingRecords;
        Module Where Defined: TresselLinker
        Data Type: arrayList
        Local or Global: Global
        Purpose: Holds the linkingrecords that is to be send in to the table;
        #*/
        private ArrayList linkingRecords = new ArrayList();
        /*#
        Variable Name: programName
        Module Where Defined: TresselLinker
        Data Type: string
        Local or Global: Global
        Purpose: Holds the program name (1st module)
        #*/
        private string programName;
        /*#
        Variable Name: programNameDefined
        Module Where Defined: TresselLinker
        Data Type: string
        Local or Global: Global
        Purpose: Flag to determine whether or not the module name should be set to the program name.
        #*/
        private bool firstModuleDone = false;
        /*#
        Variable Name: executionStartAddress
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the execution start address for program
        #*/
        private int executionStartAddress;
        /*#
        Variable Name: executionStartDefined
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Determines if execution start address has been defined.
        #*/
        private bool executionStartDefined = false;
        /*#
        Variable Name: headerRecordFound
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Determines if a header record has been found. 
                 True as soon as one's found and flipped to false if an end record is found.
        #*/
        private bool headerRecordFound;
        /*#
        Variable Name: linkerStartAddress
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the address in which the linker starts.
        #*/
        private int linkerStartAddress;

        // SYMBOL TABLE VARIABLES //
        /*#
        Variable Name: adjustment
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Provides an easy way to keep track of the current adjustment 
                 (opposed to looking values up in table to compute it).
        #*/
        private int adjustment;
        /*#
        Variable Name: linkerComputedAddress
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the linker computed address to add to symbol table (all in one go at end of module check)
        #*/
        private int linkerComputedAddress;
        /*#
        Variable Name: assemblerAssignedAddress
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the assembler assigned address to add to symbol table (all in one go at end of module check)
        #*/
        private int assemblerAssignedAddress;
        /*#
        Variable Name: assemblerAssignedEndAddress
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the assembler assigned end address.
        #*/
        private int assemblerAssignedEndAddress;

        // CURRENT MODULE VARIABLES //
        /*#
        Variable Name: currentModuleName
        Module Where Defined: TresselLinker
        Data Type: string
        Local or Global: Global
        Purpose: Holds the current module's name
        #*/
        private string currentModuleName = "";
        /*#
        Variable Name: currentLocationCounter
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: holds the current progress (LC) in object file
        #*/
        private int currentLocationCounter = 0;
        /*#
        Variable Name: currentLineNumber
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: holds the current line number (used mainly for easy error output with original source)
        #*/
        private int currentLineNumber = 0;
        /*#
        Variable Name: currentModuleLength
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: holds the actual current length of the current module. 
                 This is checked against the declared length in the header record.
        #*/
        private int currentModuleLength = 0;
        /*#
        Variable Name: currentModuleExpectedLength
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: holds the declared length of the current module. 
                 This is checked against the actual length.
        #*/
        private int currentModuleExpectedLength = 0;
        /*#
        Variable Name: expectedTotalNumRecords
        Module Where Defined: TresselLinker
        Data Type: DataTable
        Local or Global: Global
        Purpose: Holds the value of the declared number of records in object file
        #*/
        private int expectedTotalNumRecords = 0;

        //Tables
        /*#
        Variable Name: symbolTable
        Module Where Defined: TresselLinker
        Data Type: DataTable
        Local or Global: Global
        Purpose: This is the symbolTable for the linker. It will have the following 5 categories:
                SYMBOL | ASSEMBLER ADDRESS | LINKER ADDRESS | LENGTH | ADJUSTMENT
        #*/
        private DataTable symbolTable = new DataTable();
        /*#
        Variable Name: instructionsDirectivesTable
        Module Where Defined: TresselLinker
        Data Type: DataTable
        Local or Global: Global
        Purpose: Instructions Directives Table that is used to check valid op-codes in TextRecord.
        #*/
        private DataTable instructionsDirectivesTable = new DataTable();
        /*#
        Variable Name: errorMessageTable
        Module Where Defined: TresselLinker
        Data Type: DataTable
        Local or Global: Global
        Purpose: Holds the list of all error messages that could be thrown in linker.
        #*/
        private DataTable errorMessageTable;
        /*#
        Variable Name: errorTable
        Module Where Defined: TresselLinker
        Data Type: HashTable
        Local or Global: Global
        Purpose: Keeps track of errors that occur and line number where it occurs.
        #*/
        private DataTable errorTable = new DataTable();


        //Header Parse Variables
        /*#
        Variable Name: numLinkingRecords
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the number of linking records found.
        #*/
        private int numTextRecords;
        /*#
        Variable Name: numTextRecords
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Holds the number of text records found.
        #*/
        private int numLinkingRecords;
        /*#
        Variable Name: expectedNumLinkRecs
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Saves the number of expected number of linking records (from header)
        #*/
        private int expectedNumLinkRecs;
        /*#
        Variable Name: expectedNumTextRecs
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Saves the number of expected number of text records (from header)
        #*/
        private int expectedNumTextRecs;
        /*#
        Variable Name: executionStart
        Module Where Defined: TresselLinker
        Data Type: int
        Local or Global: Global
        Purpose: Saves the position/address in which the main module will start from.
        #*/
        private int executionStart;

        //Linking Record Parse Variables


        //Properties
        
        public bool ErrorFlag
        {
            #region preamble
            /*!
            Procedure Name: ErrorFlag
            Description: Property that allows for public setting/getting of errorFlag.
            Input Parameters: none
            Output Parameters: returns errorFlag
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion
            get
            {
                return errorFlag;
            }
            set
            {
                errorFlag = value;
            }
        }
        public int CurrentLocationCounter
        {
            #region preamble
            /*!
            Procedure Name: CurrentLocationCounter
            Description:    Property that allows for public setting/getting of currentLocationCounter.
                            This is used to check syntax errors in object file (memory location)
            Input Parameters: none
            Output Parameters: int - current location 
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/19/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion
            get
            {
                return currentLocationCounter;
            }
            set
            {
                currentLocationCounter = value;
            }
        }
        public ArrayList TextRecords
        {
            #region preamble
            /*!
            Procedure Name: TextRecords
            Description: Property that allows for public setting/getting of textRecords.  Contains an array of textRecords that will be in the linking file.
            Input Parameters: none
            Output Parameters: returns a textRecord 
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: SH and RM
            Procedure Creation Date: 11/19/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion
            get
            {
                return textRecords;
            }
            set
            {
                textRecords = value;
            }
        }
        public string CurrentModuleName
        {
            #region preamble
            /*!
            Procedure Name: CurrentModuleName
            Description: Property that allows for public getting of the current module name.
            Input Parameters: none
            Output Parameters: returns errorFlag
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/20/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion

            get
            {
                return currentModuleName;
            }
        }
        public string HeaderRecord
        {
            #region preamble
            /*!
            Procedure Name: HeaderRecord
            Description: Property that allows for getting of HeaderRecord
            Input Parameters: none
            Output Parameters: returns HeaderRecord
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion
            get
            {
                return headerRecord;
            }
        }
        public string EndRecord
        {
            #region preamble
            /*!
            Procedure Name: EndRecord
            Description: Gives public access to the EndRecord (to be used by the Simulator)
            Input Parameters: none
            Output Parameters: returns EndRecord
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/22/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion
            get
            {
                return endRecord;
            }
        }
        public bool FatalErrorFlag
        {
            #region preamble
            /*!
            Procedure Name: FatalErrorFlag
            Description: Property to give public access to the fatal error flag
            Input Parameters: none
            Output Parameters: bool 
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/22/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            set
            {
                fatalErrorFlag = value;
            }
            get
            {
                return fatalErrorFlag;
            }
        }
        public DataTable ErrorTable
        {
            #region preamble
            /*!
            Procedure Name: ErrorTable
            Description: Property to give public access to the errorTable
            Input Parameters: none
            Output Parameters: DataTable
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/24/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            get
            {
                return errorTable;
            }
            #endregion
        }
        public DataTable SymbolTable
        {
            #region preamble
            /*!
            Procedure Name: SymbolTable
            Description: Property to give public access to the symbolTable
            Input Parameters: none
            Output Parameters: DataTable 
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/24/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            get
            {
                return symbolTable;
            }
        }
        public int CurrentLineNumber
        {
            #region preamble
            /*!
            Procedure Name: CurrentLineNumber
            Description: Property to give public access to the current line number
            Input Parameters: none
            Output Parameters: int 
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/24/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            get
            {
                return currentLineNumber;
            }
        }

        ///////////END OF VARIABLES/PROPERTIES//////////////


        public TresselLinker(string objectSource)
        {
            #region preamble
            /*!
            Procedure Name: TresselLinker
            Description:
            Input Parameters: string objectSource
            Output Parameters:
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: splitChars
            Module Where Defined: TresselLinker
            Data Type: char[]
            Local or Global: Local
            Purpose: holds split characters.
            #*/
            char[] splitChars = { System.Environment.NewLine[0] };
            objectSourceArray = objectSource.Trim().Split(splitChars);
            linkerComputedAddress = 0;
            adjustment = 0;
        }

        public void Initialize()
        {
            #region preamble
            /*!
            Procedure Name: Initialize
            Description:    Created to intialize the TresselLinker. This is a separate method from 
                            constructor to avoid issues since object may not be "complete."
            Input Parameters: string objectSource
            Output Parameters:
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/23/09
            Why: Path names were incorrect and Parse1 was never called.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            instructionsDirectivesTable = LoadTable(System.IO.Path.GetDirectoryName(Application.ExecutablePath) + "\\InstructionsDirectivesTable.xls");
            errorMessageTable = LoadTable(System.IO.Path.GetDirectoryName(Application.ExecutablePath) + "\\LinkerErrorMessageTable.xls");
            textRecords = new ArrayList();
            InitializeErrorTable();
            InitializeSymbolTable();
            Parse1();
        }

        private void Parse1()
        {
            #region preamble
            /*!
            Procedure Name: Parse1
            Description:
            Input Parameters:
            Output Parameters:
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/20/2009
            Why: It wasn't finished... Started work on "H|"
            Who: Greg Loesch
            When: 11/21/2009
            Why: It wasn't finished... Added more to case "H|"
            Who: Greg Loesch
            When: 11/21/2009
            Why: It wasn't finished... Started working on other cases and added error checking to H|
            Who: Greg Loesch
            When: 11/22/2009
            Why: Forgot to increment text/linking records (global variables that check if header and actual match)
            Who: Greg Loesch
            When: 11/23/2009
            Why: Several changes. Now checks for memory out of bounds and sets error flags for any error in the header flag.
                Also added End record parsing/check
            Who: Greg Loesch
            When: 11/24/09
            Why: Updates adjustment variable so table lookups don't need to be done.
            Who: Greg Loesch
            When: 11/29/09
            Why: If the end parse failed, it didn't always fatal (parseSuccessfulEnd never was used). Also fixed issue when End record was never found.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            for (int i = 0; (i < objectSourceArray.Length) && !fatalErrorFlag; i++)
            {
                objectSourceArray[i] = objectSourceArray[i].Trim();
                if (objectSourceArray[i].Length >= 2)
                {
                    switch (objectSourceArray[i].Substring(0, 2))
                    {
                        case "H|":
                            {
                                if (!headerRecordFound)
                                {
                                    /*#
                                    Variable Name: parseSuccessful
                                    Module Where Defined: Parse1
                                    Data Type: bool
                                    Local or Global: Local
                                    Purpose: flags whether or not the parse is successful.
                                    #*/
                                    bool parseSuccessful = ParseHeader(objectSourceArray[i].Trim().Substring(2));
                                    if (!parseSuccessful)
                                    {
                                        headerRecord = "";
                                        fatalErrorFlag = true;
                                        break;
                                    }
                                    if (!firstModuleDone)
                                    {
                                        adjustment = assemblerAssignedAddress;
                                        linkerComputedAddress = assemblerAssignedAddress;
                                    }
                                    else
                                    {
                                        adjustment = linkerComputedAddress - assemblerAssignedAddress;
                                    }

                                    headerRecordFound = true;
                                }
                                else
                                {
                                    AddError(28, currentLineNumber); //Missing End Record
                                    fatalErrorFlag = true;
                                    break;
                                }
                            }
                            break;
                        case "T|":
                            {
                                if (headerRecordFound)
                                {
                                    TextRecord tRecord = new TextRecord(objectSourceArray[i], this);
                                    textRecords.Add(tRecord);
                                    assemblerAssignedEndAddress = tRecord.Address;
                                }
                                else
                                {
                                    AddError(29, currentLineNumber);
                                    fatalErrorFlag = true;
                                }
                                numTextRecords++;
                            }
                            break;
                        case "L|":
                            {
                                /*#
                                Variable Name: parseSuccessfulLink
                                Module Where Defined: Parse1
                                Data Type: bool
                                Local or Global: Local
                                Purpose: Flags whether or not parse of linker record was successful
                                #*/
                                bool parseSuccessfulLink = ParseLinkingRecord(objectSourceArray[i].Substring(2));
                                if (!parseSuccessfulLink)
                                {
                                    //Throw the linking record out
                                    AddError(31, currentLineNumber);
                                }
                                numLinkingRecords++;
                            }
                            break;
                        case "E|":
                            {
                                /*#
                                Variable Name: parseSuccessfulEnd
                                Module Where Defined: Parse1
                                Data Type: bool
                                Local or Global: Local
                                Purpose: Flags whether or not parse of end record was successful
                                #*/
                                bool parseSuccessfulEnd = ParseEndRecord(objectSourceArray[i].Substring(2));
                                if (!parseSuccessfulEnd)
                                {
                                    fatalErrorFlag = true;
                                }
                                else if (expectedNumLinkRecs != numLinkingRecords)
                                {
                                    AddError(25, currentLineNumber);
                                    fatalErrorFlag = true;
                                }
                                else if (expectedNumTextRecs != numTextRecords)
                                {
                                    AddError(26, currentLineNumber);
                                    fatalErrorFlag = true;
                                }
                                if (textRecords.Count > 0)
                                {
                                    if (((((TextRecord)textRecords[textRecords.Count - 1]).Address - assemblerAssignedAddress) + 1) != currentModuleExpectedLength)
                                    {
                                        /*  See errata report
                                         * 
                                         *   fatalErrorFlag = true;
                                         *   AddError(27, currentLineNumber); 
                                         */
                                    }
                                }

                                if (!fatalErrorFlag)
                                {
                                    
                                    currentModuleLength = assemblerAssignedEndAddress - assemblerAssignedAddress + 1;
                                    InsertIntoSymbolTable(currentModuleName, "Pgm_Name", assemblerAssignedAddress.ToString(), linkerComputedAddress.ToString(),
                                                          currentModuleLength.ToString(), (linkerComputedAddress - assemblerAssignedAddress).ToString());
                                    for (int j = 0; j < linkingRecords.Count; j++)
                                    {
                                        InsertIntoSymbolTable(((string[])linkingRecords[j])[0], "Entry_Sym", ((string[])linkingRecords[j])[1], ((string[])linkingRecords[j])[2], "", "");
                                    }
                                    linkerComputedAddress = linkerComputedAddress + currentModuleLength;
                                    numLinkingRecords = 0;
                                    numTextRecords = 0;
                                    currentModuleLength = 0;
                                    headerRecordFound = false;
                                    firstModuleDone = true;
                                }
                            }
                            break;
                        default:
                            {
                                AddError(7, currentLineNumber);
                                fatalErrorFlag = true;
                            }
                            break;
                    }
                }
                else
                {
                    AddError(7, currentLineNumber);
                    fatalErrorFlag = true;
                }
                currentLineNumber++;
            }  // end for loop
            if (headerRecordFound)
            {
                AddError(28, currentLineNumber-1);
                fatalErrorFlag = true;
            }
            if (!fatalErrorFlag)
            {
                endRecord = "LE|" + (2 + textRecords.Count).ToString();

                if (numTextRecords > 2047)
                {
                    AddError(14, currentLineNumber);
                }
                else
                {
                    endRecord = "LE|" + Converter.ToHex(textRecords.Count + 2).PadLeft(4, '0');
                    Parse2();
                } 
            }
        }

        private void Parse2()
        {
            #region preamble
            /*!
            Procedure Name: Parse2
            Description:
            Input Parameters:
            Output Parameters:
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/22/09 (??)
            Why: Creates the header record
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            for (int i = 0; i < textRecords.Count; i++)
            {
                ((TextRecord)textRecords[i]).parse();
            }
            headerRecord = GenerateLinkerHeaderRecord();
        }

        private bool ParseHeader(string hRecord)
        {
            #region preamble
            /*!
            Procedure Name: ParseHeader
            Description:    Parses a header record and determines if it's valid. It pulls and stores info to be validated at a 
                            later time if the parse is successful. If not, it will return false.
            Input Parameters: string - hRecord (header record without leading H|)
            Output Parameters: bool - true if parse was successful, false otherwise.
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/21/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/22/09
            Why: Added a regex check for the module name
            Who: Greg Loesch
            When: 11/23/09
            Why: Memory bounds checks
            Who: Greg Loesch
            When: 11/24/09
            Why: AddError method changed, so line numbers had to be added to arguments.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            // Assumes the "[char]|" has been trimmed

            /*#
            Variable Name: token
            Module Where Defined: ParseHeader
            Data Type: string
            Local or Global: Local
            Purpose: holds the next field to be processed/checked.
            #*/
            string token;
            /*#
            Variable Name: exp
            Module Where Defined: ParseHeader
            Data Type: Regex expression
            Local or Global: Local
            Purpose: Regex expression to check valid labels/module names.
            #*/
            Regex exp = new Regex(@"^[a-zA-Z][a-zA-Z0-9\._]{1,63}$");
            /*#
            Variable Name: exp
            Module Where Defined: ParseHeader
            Data Type: bool
            Local or Global: Local
            Purpose: Flags whether or not parse was successful. This is returned.
            #*/
            bool parseSuccessful = false;
            
            token = GetNextRecordField(ref hRecord); // token = module name

            if (!exp.IsMatch(token))
            {
                AddError(9, currentLineNumber);
                errorFlag = true;
            }
            else
            {
                if (!firstModuleDone)
                {
                    programName = token;
                }

                if (!IsInSymbolTable(token).Equals(""))
                {
                    AddError(35, currentLineNumber);
                    errorFlag = true;
                }
                else
                {
                    currentModuleName = token;
                }

                token = GetNextRecordField(ref hRecord); // token = Date
                if (token.Length != 8) // 8 characters (yyyy:ddd) - Length is all we really care about.
                {
                    AddError(10, currentLineNumber);
                    errorFlag = true;
                }
                else if (!errorFlag)
                {
                    token = GetNextRecordField(ref hRecord); // token = Time
                    if (token.Length != 8)
                    {
                        AddError(11, currentLineNumber);
                        errorFlag = true;
                    }
                    else if (!errorFlag)
                    {
                        token = GetNextRecordField(ref hRecord); // token = program length (hex)
                        if (token.Length != 4)
                        {
                            AddError(12, currentLineNumber);
                            errorFlag = true;
                        }
                        else if (!errorFlag)
                        {
                            currentModuleExpectedLength = Converter.ToInt(token, false);
                            if (currentModuleExpectedLength < 0)
                            {
                                AddError(13, currentLineNumber);
                                errorFlag = true;
                                currentModuleExpectedLength = 0;
                            }
                            else
                            {
                                if (currentModuleExpectedLength > 2047)
                                {
                                    AddError(14, currentLineNumber);
                                    errorFlag = true;
                                }
                            }
                            token = GetNextRecordField(ref hRecord); // token = assembler assigned program load (hhhh)
                            if (token.Length != 4)
                            {
                                AddError(16, currentLineNumber);
                                errorFlag = true;
                            }
                            else if (!errorFlag)
                            {
                                assemblerAssignedAddress = Converter.ToInt(token, false);
                                
                                if (!firstModuleDone)
                                {
                                    linkerStartAddress = assemblerAssignedAddress;
                                    currentLocationCounter = assemblerAssignedAddress;
                                    assemblerAssignedAddress = linkerStartAddress;
                                }
                                assemblerAssignedEndAddress = assemblerAssignedAddress - 1; // Takes care of issue with no text records (and end-start+1)...
                                if (assemblerAssignedAddress < 0 || assemblerAssignedAddress > 2047)
                                {
                                    if (assemblerAssignedAddress == -1)
                                    {
                                        AddError(17, currentLineNumber);
                                        errorFlag = true;
                                        assemblerAssignedAddress = 0;
                                    }
                                    else
                                    {
                                        AddError(14, currentLineNumber);
                                        errorFlag = true;
                                        assemblerAssignedAddress = 0;
                                    }
                                }
                                if (((assemblerAssignedAddress + currentModuleExpectedLength) > 2047) && !errorFlag)
                                {
                                    AddError(14, currentLineNumber);
                                    errorFlag = true;
                                }
                                else if (!errorFlag)
                                {
                                    token = GetNextRecordField(ref hRecord); // token = number of linking records (hhhh)

                                    if (token.Length != 4)
                                    {
                                        AddError(21, currentLineNumber);
                                    }
                                    else
                                    {
                                        expectedNumLinkRecs = Converter.ToInt(token, false);
                                        if (expectedNumLinkRecs < 0)
                                        {
                                            AddError(21, currentLineNumber);
                                            errorFlag = true;
                                            expectedNumLinkRecs = 0;
                                        }
                                        else
                                        {
                                            token = GetNextRecordField(ref hRecord); // token = number of text records (hhhh)

                                            if (token.Length != 4)
                                            {
                                                AddError(22, currentLineNumber);
                                            }
                                            else
                                            {
                                                expectedNumTextRecs = Converter.ToInt(token, false);
                                                if (expectedNumTextRecs < 0)
                                                {
                                                    AddError(22, currentLineNumber);
                                                    errorFlag = true;
                                                    expectedNumTextRecs = 0;
                                                }
                                                else
                                                {
                                                    token = GetNextRecordField(ref hRecord); // token = Execution start address
                                                    if (token.Length != 4)
                                                    {
                                                        AddError(23, currentLineNumber);
                                                        errorFlag = true;
                                                    }
                                                    else
                                                    {
                                                        
                                                        if (!executionStartDefined)
                                                        {
                                                            executionStartAddress = Converter.ToInt(token, false);
                                                            executionStartDefined = true;
                                                            if (executionStartAddress < 0)
                                                            {
                                                                AddError(23, currentLineNumber);
                                                                errorFlag = true;
                                                                executionStartAddress = 0;
                                                            }
                                                            else
                                                            {
                                                                if (hRecord.Length >= 11)
                                                                {
                                                                    parseSuccessful = true;
                                                                }
                                                                else
                                                                {
                                                                    AddError(24, currentLineNumber);
                                                                }
                                                            }
                                                        }
                                                        else
                                                        {
                                                            // "ignore" alt start of module and check rest of record info
                                                            if (hRecord.Length >= 11)
                                                            {
                                                                parseSuccessful = true;
                                                            }
                                                            else
                                                            {
                                                                AddError(24, currentLineNumber);
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return parseSuccessful;
        }

        private string GenerateLinkerHeaderRecord()
        {
            #region preamble
            /*!
            Procedure Name: GenerateLinkerHeaderRecord
            Description: Creates the header record for linker (in string form). If 
            Input Parameters: none
            Output Parameters: valid header record
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/22/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/29/09
            Why: Forgot a field in the header... added load address (before dates)
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            return  "LH|" + programName + "|" +
                    Converter.ToHex(executionStartAddress).PadLeft(4, '0') + "|" +
                    Converter.ToHex(currentLocationCounter-linkerStartAddress).PadLeft(4, '0') + "|" +
                    Converter.ToHex(linkerStartAddress).PadLeft(4, '0') + "|" +
                    DateTime.Now.Year.ToString() + ":" +
                    DateTime.Now.DayOfYear.ToString().PadLeft(3, '0') + "|" +
                    DateTime.Now.Hour.ToString().PadLeft(2, '0') + ":" +
                    DateTime.Now.Minute.ToString().PadLeft(2, '0') + ":" +
                    DateTime.Now.Second.ToString().PadLeft(2, '0') + "|" +
                    "TRESSEL-LINK|1.0|0";

        }

        private bool ParseLinkingRecord(string linkRecord)
        {
            #region preamble
            /*!
            Procedure Name: ParseLinkingRecord
            Description: Parses a Linking Record from the original object file
            Input Parameters: string
            Output Parameters: bool - true if parse is succesful, false otherwise
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/21/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: exp
            Module Where Defined: ParseLinkingRecord
            Data Type: Regex expression
            Local or Global: Local
            Purpose: Regex expression to check valid labels/module names.
            #*/
            Regex exp = new Regex(@"^[a-zA-Z][a-zA-Z0-9\._]{1,63}$");
            /*#
            Variable Name: entryAddress
            Module Where Defined: ParseLinkingRecord
            Data Type: int
            Local or Global: Local
            Purpose: holds the address of the entry.
            #*/
            int entryAddress = 0;
            /*#
            Variable Name: entryName
            Module Where Defined: ParseLinkingRecord
            Data Type: string
            Local or Global: Local
            Purpose: holds the name of the entry.
            #*/
            string entryName = GetNextRecordField(ref linkRecord); //Expects leading "L|" to be trimmed off

            if (!exp.IsMatch(entryName))
            {
                errorFlag = true;
                AddError(32, currentLineNumber);
            }
            else
            {
                entryAddress = Converter.ToInt(GetNextRecordField(ref linkRecord), false);
                if (entryAddress < 0)
                {
                    AddError(3, currentLineNumber);
                    errorFlag = true;
                }
                else
                {
                    if ((entryAddress < assemblerAssignedAddress) || 
                        (entryAddress > assemblerAssignedAddress+currentModuleExpectedLength))
                    {
                        AddError(14, currentLineNumber);
                        errorFlag = true;
                    }
                    else
                    {
                        /*#
                        Variable Name: lRecordModuleName
                        Module Where Defined: ParseLinkingRecord
                        Data Type: string
                        Local or Global: Local
                        Purpose: simply holds field to be checked (with current module name)
                        #*/
                        string lRecordModuleName = GetNextRecordField(ref linkRecord);
                        if (!lRecordModuleName.Equals(currentModuleName))
                        {
                            AddError(33, currentLineNumber);
                            fatalErrorFlag = true;
                            errorFlag = true;
                        }
                        else
                        {
                            
                            string[] temp = new string[3]{entryName,entryAddress.ToString(),(entryAddress + adjustment).ToString()};
                            linkingRecords.Add(temp);//asdf
                        }
                    }
                }
            }
            return !errorFlag;
        }

        private bool ParseEndRecord(string endRecord)
        {
            #region preamble
            /*!
            Procedure Name: ParseEndRecord
            Description: Parses an End Record from the original object file
            Input Parameters: string
            Output Parameters: bool - true if parse is succesful, false otherwise
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/23/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            // E| has been stripped

            /*#
            Variable Name: token
            Module Where Defined: ParseEndRecord
            Data Type: string
            Local or Global: Local
            Purpose: holds the field to be checked
            #*/
            string token = GetNextRecordField(ref endRecord); // token = total # records in hex
            if (token.Length != 4)
            {
                AddError(34, currentLineNumber);
                errorFlag = true;
                fatalErrorFlag = true;
            }
            else
            {
                expectedTotalNumRecords = Converter.ToInt(token, false);
                if (expectedTotalNumRecords < 0)
                {
                    AddError(34, currentLineNumber);
                    errorFlag = true;
                }
                else
                {
                    token = GetNextRecordField(ref endRecord);
                    if (!token.Equals(currentModuleName))
                    {
                        AddError(33, currentLineNumber);
                        errorFlag = true;
                        fatalErrorFlag = true;
                    }
                }
            }
            return !errorFlag;
        }

        private string GetNextRecordField(ref string record)
        {
            #region preamble
            /*!
            Procedure Name: GetNextRecordField
            Description: Grabs the next field in the object file (fields are delimited by |'s).
            Input Parameters: string - record passed by reference (so it's altered)
            Output Parameters: the next field
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/22/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/23/09 (??)
            Why: Had an array out of bounds issue. Also "cleaned" up the code so that it removed "|" in a logical manner.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: field
            Module Where Defined: GetNextRecordField
            Data Type: string
            Local or Global: Local
            Purpose: holds the field value (this is returned).
            #*/
            string field = "";
             /*#
            Variable Name: done
            Module Where Defined: GetNextRecordField
            Data Type: bool
            Local or Global: Local
            Purpose: Flag to determine whether or not the next field is complete.
            #*/
            bool done = false;
            while (!done)
            {
                if (record.Length == 0)
                {
                    done = true;
                }
                else
                {
                     if (record[0] == '|')
                    {
                        record = record.Substring(1);
                        done = true;
                    }
                    else
                    {
                        field = field + record[0];
                        record = record.Substring(1);
                    }
                }
            }
            return field;
        }

        public void AddError(int errorNumber, int lineNumber)
        {
            #region preamble
            /*!
            Procedure Name: AddError
            Description: Adds an error to the error map with the defined line number.
            Input Parameters: int - errorNumber, int - lineNumber
            Output Parameters: none
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/20/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/24/09
            Why: Now takes in the line number, too.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: newError
            Module Where Defined: AddError
            Data Type: string[]
            Local or Global: Local
            Purpose: Holds the three fields that are to be added to the error table.
            #*/
            string[] newError = new string[3];
             /*#
            Variable Name: found
            Module Where Defined: AddError
            Data Type: DataRow[]
            Local or Global: Local
            Purpose: Holds the row that's looked up.
            #*/
            DataRow[] found = errorMessageTable.Select("Number='"+errorNumber.ToString()+"'");

            newError[0] = lineNumber.ToString();
            newError[1] = found[0]["Description"].ToString();
            newError[2] = found[0]["Type"].ToString();

            errorTable.Rows.Add(newError);
        }

        // Table Helper Methods
        private void InsertIntoSymbolTable(string symbol, string type, string assemblerAddress, string linkerAddress, string length, string adjustment)
        {
            #region preamble
            /*!
            Procedure Name: InsertIntoSymbolTable
            Description:
            SYMBOL | ASSEMBLER ADDRESS | LINKER ADDRESS | LENGTH | ADJUSTMENT
            Input Parameters:
            Output Parameters:
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch and Louis Pan
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/23/09
            Why: Added type.
            Who: Greg Loesch
            When: 11/24/09
            Why: symbolTable.Rows.Add(row) instead of import
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: row
            Module Where Defined: InsertIntoSymbolTable
            Data Type: DataRow
            Local or Global: Local
            Purpose: Created row that will be inserted into the table.
            #*/
            DataRow row = this.symbolTable.NewRow();
            row["SYMBOL"] = symbol;
            row["TYPE"] = type;
            row["ASSEMBLER ADDRESS"] = assemblerAddress;
            row["LINKER ADDRESS"] = linkerAddress;
            row["LENGTH"] = length;
            row["ADJUSTMENT"] = adjustment;

            symbolTable.Rows.Add(row);
        }

        public DataTable LoadTable(string excelFile)
        {
            #region preamble
            /*!
            Procedure Name: LoadTable (copied from tressel intermediate)
            Description: converts an excel file to a datatable
            Input Parameters: string excelFile
            Output Parameters: DataTable
            Errors Conditions Tested:
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 10/13/2009
            Modification Log:
            Who: 
            When:
            Why: 
            Coding standards met: SR
            Testing standards met: RM
            !*/
            #endregion

            /*#
            Variable Name: excelConnectionStr
            Module Where Defined: LoadTable
            Data Type: string
            Local or Global: Local
            Purpose: used to initialize the OleDBConnection object to connect to the excel file
            #*/
            string excelConnectionStr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source="
                + excelFile + @";Extended Properties=""Excel 8.0;HDR=YES""";
            bool test = System.IO.File.Exists(excelFile);
            /*#
            Variable Name: con
            Module Where Defined: LoadTable
            Data Type: OleDbConnection
            Local or Global: Local
            Purpose: used to connect to the excel file as a database
            #*/
            OleDbConnection con = new OleDbConnection(excelConnectionStr); // create a connection object
            /*#
            Variable Name: dt
            Module Where Defined: LoadTable
            Data Type: DataTable
            Local or Global: Local
            Purpose: holds the contents of the excel file
            #*/
            DataTable dt = new DataTable();
            con.Open();
            /*#
            Variable Name: adapter
            Module Where Defined: LoadTable
            Data Type: OleDbDataAdapter
            Local or Global: Local
            Purpose: holds the contents of the excel file before it is put into the table
            #*/
            OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Sheet1$]", con);
            adapter.Fill(dt);
            con.Close();
            return dt;
        }

        private void InitializeSymbolTable()
        {
            #region preamble
            /*!
            Procedure Name: InitializeSymbolTable()
            Description: Initializes the symbol table (symbolTable) with the proper field names
                         SYMBOL | ASSEMBLER ADDRESS | LINKER ADDRESS | LENGTH | ADJUSTMENT
            Input Parameters: none
            Output Parameters: none
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/23/09
            Why: Added Type.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            /*#
            Variable Name: myDataColumn
            Module Where Defined: InitializeSymbolTable
            Data Type: DataColumn
            Local or Global: Local
            Purpose: General purpose column used to create the symbol table.
            #*/
            DataColumn myDataColumn;

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "SYMBOL";
            myDataColumn.ReadOnly = false;
            symbolTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "TYPE";
            myDataColumn.ReadOnly = false;
            symbolTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "ASSEMBLER ADDRESS";
            symbolTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "LINKER ADDRESS";
            symbolTable.Columns.Add(myDataColumn);


            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "LENGTH";
            symbolTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "ADJUSTMENT";
            symbolTable.Columns.Add(myDataColumn);

        }

        private void InitializeErrorTable()
        {
            #region preamble
            /*!
            Procedure Name: InitializeErrorTable()
            Description: Initializes/creates the error table with the proper field names
                         SYMBOL | ASSEMBLER ADDRESS | LINKER ADDRESS | LENGTH | ADJUSTMENT
            Input Parameters: none
            Output Parameters: none
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/18/2009
            Modification Log:
            Who: 
            When: 
            Why: 
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            /*#
            Variable Name: myDataColumn
            Module Where Defined: InitializeErrorTable
            Data Type: DataColumn
            Local or Global: Local
            Purpose: General purpose column used to create the Error table.
            #*/
            DataColumn myDataColumn;

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "LINE";
            errorTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "MESSAGE";
            errorTable.Columns.Add(myDataColumn);

            myDataColumn = new DataColumn();
            myDataColumn.DataType = System.Type.GetType("System.String");
            myDataColumn.ColumnName = "TYPE";
            errorTable.Columns.Add(myDataColumn);
        }

        public int GetValueFromSymbolTable(string label, string columnName)
        {
            #region preamble
            /*!
            Procedure Name: GetValueFromSymbolTable
            Description:    Looks up the label in the symbol table and returns the specified column value (in int form). 
                            This assumes that the label passed is an actual label (not a string value of an address, length, etc.)
            Input Parameters: string label, columnName
            Output Parameters: int - table value
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/19/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/19/09
            Why: Made method more general. Now takes in a column name, too.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            return int.Parse(symbolTable.Select("SYMBOL='" + label + "'")[0][columnName].ToString());
        }

        public string IsInSymbolTable(string label)
        {
            #region preamble
            /*!
            Procedure Name: IsInSymbolTable
            Description: Looks up the label in the symbol table and returns:
                            either the symbol type or "" if not found.
            Input Parameters: string label
            Output Parameters: int
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/19/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/21/09
            Why: Now returns an int instead of bool depending on three states stated above.
            Who: Greg Loesch
            When: 11/23/09
            Why: Now returns the symbol type or "" if not found.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion

            if (symbolTable.Select("SYMBOL='" + label + "'").Length > 0)
            {
                return symbolTable.Select("SYMBOL='" + label + "'")[0]["TYPE"].ToString();
                
            }
            return "";
        }

        public string GetOpCode(string binary)
        {
            #region preamble
            /*!
            Procedure Name: GetOpCode
            Description: returns the string form of the binary opcode
            Input Parameters: string binary
            Output Parameters: string
            Error Conditions Tested: 
            Error Messages Generated:
            Original Author: Greg Loesch
            Procedure Creation Date: 11/19/2009
            Modification Log:
            Who: Greg Loesch
            When: 11/21/09
            Why: Now returns an int instead of bool depending on three states stated above.
            Who: Greg Loesch
            When: 11/23/09
            Why: Now returns the symbol type or "" if not found.
            Coding standards met: SR
            Testing standards met:RM
            !*/
            #endregion
            /*#
            Variable Name: temp
            Module Where Defined: GetOpCode
            Data Type: DataRow[]
            Local or Global: Local
            Purpose: Holds Data Rows returned from table "search"
            #*/
            DataRow[] temp = instructionsDirectivesTable.Select("BINARY='" + binary.Trim() + "'");
            if (temp.Length != 0)
            {
                return temp[0]["OPCODE"].ToString();
            }
            else
            {
                return "";
            }
        }
    }
}
